import numpy as np
import pandas as pd
from typing import Union, Tuple, Dict, Optional
from warnings import warn

class EntropyWeightCalculator:
    """熵权法计算类，用于计算指标权重"""
    
    def __init__(self, eps: float = 1e-12):
        """
        初始化熵权法计算器
        
        参数:
            eps: 防止log(0)的极小常数
        """
        self.eps = eps
        
    def calculate_weights(self, df: pd.DataFrame, columns: list) -> Dict[str, float]:
        """
        计算指标的熵权法权重
        
        参数:
            df: 包含指标数据的 DataFrame
            columns: 指标列名称列表
            
        返回:
            包含权重的字典，键为列名，值为权重
        """
        data = df[columns].copy()
        data.replace([np.inf, -np.inf], np.nan, inplace=True)  # 替换无穷值
        data.dropna(axis=1, how='any', inplace=True)  # 删除含有无穷值的列
        data.dropna(axis=0, how='any', inplace=True)  # 删除含有缺失值的行
        
        if data.empty or data.columns.empty:
            warn("数据为空或没有有效指标列，返回均等权重")
            return {col: 1/len(columns) for col in columns}
        
        # 数据一致性检查
        if not all(data.dtypes.apply(lambda x: x in ['int', 'float'])):
            # 尝试转换其他类型为float
            for col in data.columns:
                if data[col].dtype not in ['int', 'float']:
                    converted = pd.to_numeric(data[col], errors='coerce')
                    if converted.dtype in ['int', 'float']:
                        data[col] = converted
                    else:
                        warn(f"警告: 列 {col} 数据转换失败，已从分析中移除")
                        data.drop(columns=[col], inplace=True)
        
        # 最小-最大归一化
        normalized = self._normalize(data)
        
        # 计算比重
        p = normalized / normalized.sum(axis=0, keepdims=True) + self.eps
        
        # 计算熵值
        e = -np.sum(p * np.log(p), axis=0) / np.log(normalized.shape[0])
        
        # 计算差异度
        d = 1 - e
        
        # 计算权重
        weights = d / np.sum(d)
        
        return {col: float(weights[i]) for i, col in enumerate(columns)}
    
    def _normalize(self, data: pd.DataFrame) -> pd.DataFrame:
        """最小-最大归一化数据"""
        min_vals = data.min()
        max_vals = data.max()
        return (data - min_vals) / (max_vals - min_vals)
    
    def apply_weights(self, df: pd.DataFrame, columns: list, decimals: int = 4) -> pd.DataFrame:
        """
        应用计算出的权重
        参数:
            df: 原始数据DataFrame
            columns: 指标列名列表
            decimals: 权重小数位数
            
        返回:
            添加了权重列的DataFrame
        """
        weights = self.calculate_weights(df, columns)
        
        # 创建权重列
        weight_col = "_".join(columns) + "_EntropyWeight"
        if weight_col in df.columns:
            df.drop(columns=[weight_col], inplace=True)
        
        # 计算加权得分
        weight_df = pd.DataFrame({col: df[col] * weights[col] for col in columns})
        df["_".join(columns) + "_Score"] = weight_df.sum(axis=1)
        
        # 添加权重详情
        for col, w in weights.items():
            df[col + "_EntropyWeight"] = round(w, decimals)
        
        return df

def entropy_weight(df: pd.DataFrame, columns: list, decimals: int = 4) -> Dict[str, float]:
    """
    计算指标的熵权法权重
    
    参数:
        df: 包含指标数据的 DataFrame
        columns: 指标列名称列表
        decimals: 权重小数位数，用于输出显示
    
    返回:
        包含权重的字典，键为列名，值为权重
    """
    # 数据验证
    if df.empty or df.shape[0] < 2 or df.shape[1] < 1:
        warn("数据不足，无法计算熵权，返回均等权重")
        return {col: 1/len(columns) for col in columns}
    
    # 检查列是否存在
    for col in columns:
        if col not in df.columns:
            raise KeyError(f"指定的列 '{col}' 在DataFrame中不存在")
    
    # 创建计算器实例
    calculator = EntropyWeightCalculator()
    
    try:
        # 计算权重
        weights = calculator.calculate_weights(df, columns)
        
        # 转为列表并排序（从高到低）
        sorted_weights = sorted(weights.items(), key=lambda x: x[1], reverse=True)
        
        # 打印权重
        print("\n熵权法计算结果:")
        print("指标\t\t\t\t权重")
        for item, weight in sorted_weights:
            print(f"{item}\t\t\t{weight:.6f} (小数位: {decimals})")
        
        total = sum(weights.values())
        print(f"权重总和: {total:.6f}")
        if abs(total - 1.0) > 1e-5:
            print(f"注意: 权重总和不等于1，差值: {abs(total - 1.0):.6f}")
        
        # 可视化权重（如果要求）
        visualize_weights(weights)
        
        return weights
    
    except Exception as e:
        # 出错时处理
        raise ValueError(f"计算熵权法权重时出错: {str(e)}")

def visualize_weights(weights: Dict[str, float]) -> None:
    """
    可选的权重可视化函数
    参数:
        weights: 包含权重的字典
    """
    import matplotlib.pyplot as plt
    try:
        # 按权重排序的数据
        sorted_data = sorted(weights.items(), key=lambda x: x[1], reverse=True)
        names, values = zip(*sorted_data)
        
        # 创建水平条形图
        plt.figure(figsize=(10, 6))
        plt.barh(names, values, color='skyblue')
        plt.xlabel('权重')
        plt.title('熵权法计算权重')
        plt.tight_layout()
        plt.show()
    except ImportError:
        # 忽略matplotlib依赖
        pass
    except Exception as e:
        print(f"权重可视化时出错: {str(e)}")
